/*
 *  Copyright 2005 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.mybatis.generator.api;

import static org.mybatis.generator.internal.util.EqualsUtil.areEqual;
import static org.mybatis.generator.internal.util.HashCodeUtil.SEED;
import static org.mybatis.generator.internal.util.HashCodeUtil.hash;
import static org.mybatis.generator.internal.util.JavaBeansUtil.getCamelCaseString;
import static org.mybatis.generator.internal.util.StringUtility.composeFullyQualifiedTableName;
import static org.mybatis.generator.internal.util.StringUtility.stringHasValue;

import org.mybatis.generator.config.Context;

/**
 * @author Jeff Butler
 */
public class FullyQualifiedTable
{
    private String introspectedCatalog;
    
    private String introspectedSchema;
    
    private String introspectedTableName;
    
    private String runtimeCatalog;
    
    private String runtimeSchema;
    
    private String runtimeTableName;
    
    private String domainObjectName;
    
    private String domainObjectSubPackage;
    
    private String alias;
    
    private boolean ignoreQualifiersAtRuntime;
    
    private String beginningDelimiter;
    
    private String endingDelimiter;
    
    /**
     * This object is used to hold information related to the table itself, not the columns in the table.
     * 
     * @param introspectedCatalog the actual catalog of the table as returned from DatabaseMetaData. This value should only be set if the user configured a catalog. Otherwise the DatabaseMetaData is
     *            reporting some database default that we don't want in the generated code.
     * 
     * @param introspectedSchema the actual schema of the table as returned from DatabaseMetaData. This value should only be set if the user configured a schema. Otherwise the DatabaseMetaData is
     *            reporting some database default that we don't want in the generated code.
     * 
     * @param introspectedTableName the actual table name as returned from DatabaseMetaData
     * 
     * @param domainObjectName the configured domain object name for this table. If nothing is configured, we'll build the domain object named based on the tableName or runtimeTableName.
     * 
     * @param alias a configured alias for the table. This alias will be added to the table name in the SQL
     * 
     * @param ignoreQualifiersAtRuntime if true, then the catalog and schema qualifiers will be ignored when composing fully qualified names in the generated SQL. This is used, for example, when the
     *            user needs to specify a specific schema for generating code but does not want the schema in the generated SQL
     * 
     * @param runtimeCatalog this is used to "rename" the catalog in the generated SQL. This is useful, for example, when generating code against one catalog that should run with a different catalog.
     * 
     * @param runtimeSchema this is used to "rename" the schema in the generated SQL. This is useful, for example, when generating code against one schema that should run with a different schema.
     * 
     * @param runtimeTableName this is used to "rename" the table in the generated SQL. This is useful, for example, when generating code to run with an Oracle synonym. The user would have to specify
     *            the actual table name and schema for generation, but would want to use the synonym name in the generated SQL
     * 
     * @param delimitIdentifiers if true, then the table identifiers will be delimited at runtime. The delimiter characters are obtained from the Context.
     */
    public FullyQualifiedTable(String introspectedCatalog, String introspectedSchema, String introspectedTableName, String domainObjectName, String alias, boolean ignoreQualifiersAtRuntime,
        String runtimeCatalog, String runtimeSchema, String runtimeTableName, boolean delimitIdentifiers, Context context)
    {
        super();
        this.introspectedCatalog = introspectedCatalog;
        this.introspectedSchema = introspectedSchema;
        this.introspectedTableName = introspectedTableName;
        this.ignoreQualifiersAtRuntime = ignoreQualifiersAtRuntime;
        this.runtimeCatalog = runtimeCatalog;
        this.runtimeSchema = runtimeSchema;
        this.runtimeTableName = runtimeTableName;
        if (stringHasValue(domainObjectName))
        {
            int index = domainObjectName.lastIndexOf('.');
            if (index == -1)
            {
                this.domainObjectName = domainObjectName;
            }
            else
            {
                this.domainObjectName = domainObjectName.substring(index + 1);
                this.domainObjectSubPackage = domainObjectName.substring(0, index);
            }
        }
        if (alias == null)
        {
            this.alias = null;
        }
        else
        {
            this.alias = alias.trim();
        }
        beginningDelimiter = delimitIdentifiers ? context.getBeginningDelimiter() : ""; //$NON-NLS-1$
        endingDelimiter = delimitIdentifiers ? context.getEndingDelimiter() : ""; //$NON-NLS-1$
    }
    
    public String getIntrospectedCatalog()
    {
        return introspectedCatalog;
    }
    
    public String getIntrospectedSchema()
    {
        return introspectedSchema;
    }
    
    public String getIntrospectedTableName()
    {
        return introspectedTableName;
    }
    
    /**
     * @return
     */
    public String getFullyQualifiedTableNameAtRuntime()
    {
        StringBuilder localCatalog = new StringBuilder();
        if (!ignoreQualifiersAtRuntime)
        {
            if (stringHasValue(runtimeCatalog))
            {
                localCatalog.append(runtimeCatalog);
            }
            else if (stringHasValue(introspectedCatalog))
            {
                localCatalog.append(introspectedCatalog);
            }
        }
        if (localCatalog.length() > 0)
        {
            addDelimiters(localCatalog);
        }
        StringBuilder localSchema = new StringBuilder();
        if (!ignoreQualifiersAtRuntime)
        {
            if (stringHasValue(runtimeSchema))
            {
                localSchema.append(runtimeSchema);
            }
            else if (stringHasValue(introspectedSchema))
            {
                localSchema.append(introspectedSchema);
            }
        }
        if (localSchema.length() > 0)
        {
            addDelimiters(localSchema);
        }
        StringBuilder localTableName = new StringBuilder();
        if (stringHasValue(runtimeTableName))
        {
            localTableName.append(runtimeTableName);
        }
        else
        {
            localTableName.append(introspectedTableName);
        }
        addDelimiters(localTableName);
        return composeFullyQualifiedTableName(localCatalog.toString(), localSchema.toString(), localTableName.toString(), '.');
    }
    
    /**
     * @return
     */
    public String getAliasedFullyQualifiedTableNameAtRuntime()
    {
        StringBuilder sb = new StringBuilder();
        sb.append(getFullyQualifiedTableNameAtRuntime());
        if (stringHasValue(alias))
        {
            sb.append(' ');
            sb.append(alias);
        }
        return sb.toString();
    }
    
    /**
     * This method returns a string that is the fully qualified table name, with underscores as the separator.
     * 
     * @return the namespace
     */
    public String getIbatis2SqlMapNamespace()
    {
        String localCatalog = stringHasValue(runtimeCatalog) ? runtimeCatalog : introspectedCatalog;
        String localSchema = stringHasValue(runtimeSchema) ? runtimeSchema : introspectedSchema;
        String localTable = stringHasValue(runtimeTableName) ? runtimeTableName : introspectedTableName;
        return composeFullyQualifiedTableName(ignoreQualifiersAtRuntime ? null : localCatalog, ignoreQualifiersAtRuntime ? null : localSchema, localTable, '_');
    }
    
    public String getDomainObjectName()
    {
        if (stringHasValue(domainObjectName))
        {
            return domainObjectName;
        }
        else if (stringHasValue(runtimeTableName))
        {
            return getCamelCaseString(runtimeTableName, true);
        }
        else
        {
            return getCamelCaseString(introspectedTableName, true);
        }
    }
    
    @Override
    public boolean equals(Object obj)
    {
        if (this == obj)
        {
            return true;
        }
        if (!(obj instanceof FullyQualifiedTable))
        {
            return false;
        }
        FullyQualifiedTable other = (FullyQualifiedTable)obj;
        return areEqual(this.introspectedTableName, other.introspectedTableName) && areEqual(this.introspectedCatalog, other.introspectedCatalog)
            && areEqual(this.introspectedSchema, other.introspectedSchema);
    }
    
    @Override
    public int hashCode()
    {
        int result = SEED;
        result = hash(result, introspectedTableName);
        result = hash(result, introspectedCatalog);
        result = hash(result, introspectedSchema);
        return result;
    }
    
    @Override
    public String toString()
    {
        return composeFullyQualifiedTableName(introspectedCatalog, introspectedSchema, introspectedTableName, '.');
    }
    
    public String getAlias()
    {
        return alias;
    }
    
    /**
     * Calculates a Java package fragment based on the table catalog and schema. If qualifiers are ignored, then this method will return an empty string
     * 
     * @return the subpackage for this table
     */
    public String getSubPackage(boolean isSubPackagesEnabled)
    {
        StringBuilder sb = new StringBuilder();
        if (!ignoreQualifiersAtRuntime && isSubPackagesEnabled)
        {
            if (stringHasValue(runtimeCatalog))
            {
                sb.append('.');
                sb.append(runtimeCatalog.toLowerCase());
            }
            else if (stringHasValue(introspectedCatalog))
            {
                sb.append('.');
                sb.append(introspectedCatalog.toLowerCase());
            }
            if (stringHasValue(runtimeSchema))
            {
                sb.append('.');
                sb.append(runtimeSchema.toLowerCase());
            }
            else if (stringHasValue(introspectedSchema))
            {
                sb.append('.');
                sb.append(introspectedSchema.toLowerCase());
            }
        }
        if (stringHasValue(domainObjectSubPackage))
        {
            sb.append('.');
            sb.append(domainObjectSubPackage);
        }
        // TODO - strip characters that are not valid in package names
        return sb.toString();
    }
    
    private void addDelimiters(StringBuilder sb)
    {
        if (stringHasValue(beginningDelimiter))
        {
            sb.insert(0, beginningDelimiter);
        }
        if (stringHasValue(endingDelimiter))
        {
            sb.append(endingDelimiter);
        }
    }
}
